/*
 * File:    HtmlReader.cs
 * Created: 01/17/2003
 * Author:  Igor Tkachev
 *          mailto:it@rsdn.ru
 */

/***************************************************************************/
/* Modified: 08/04/2007                                                    */
/* Author:   Yuri Mikhailov (bitsharp@gmail.com)                           */
/* ---------------------------------------------                           */
/* Manual handling GET request redirections                                */
/***************************************************************************/

/***************************************************************************/
/* Modified: 08/09/2007                                                    */
/* Author:   Yuri Mikhailov (bitsharp@gmail.com)                           */
/* ---------------------------------------------                           */
/* Specific to http://vkontakte.ru cookies handling                        */
/* Saving last POST/GET request time                                       */
/***************************************************************************/

/***************************************************************************/
/* Modified: 09/10/2007                                                    */
/* Author:   Yuri Mikhailov (bitsharp@gmail.com)                           */
/* ---------------------------------------------                           */
/* Post encoding changed from ASCII to windows-1251                        */
/***************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Security.Cryptography.X509Certificates;

namespace Rsdn.Framework.Common
{
    /// <summary>
    /// Encapsulates WebReader functions.
    /// </summary>
    public class HtmlReader
    {
        public HtmlReader()
        {
        }

        public HtmlReader(string baseUri)
        {
            BaseUri = baseUri;
        }

        private X509Certificate _certificate;
        public X509Certificate Certificate
        {
            get { return _certificate; }
            set { _certificate = value; }
        }

        private string _baseUri;
        public string BaseUri
        {
            get { return _baseUri; }
            set { _baseUri = value; }
        }

        private string _previousUri;
        public string PreviousUri
        {
            get { return _previousUri; }
            set { _previousUri = value; }
        }


        private Dictionary<string, string> _cookies = new Dictionary<string, string>();
        public Dictionary<string, string> Cookies
        {
            get { return _cookies; }
            set { _cookies = value; }
        }

        private string _userAgent = @"HtmlReader";
        public string UserAgent
        {
            get { return _userAgent; }
            set { _userAgent = value; }
        }

        private string _accept =
            @"image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, */*";
        public string Accept
        {
            get { return _accept; }
            set { _accept = value; }
        }

        private Uri _requestUri;
        public Uri RequestUri
        {
            get { return _requestUri; }
            set { _requestUri = value; }
        }

        private string _contentType = string.Empty;
        public string ContentType
        {
            get { return _contentType; }
            set { _contentType = value; }
        }

        private IWebProxy _proxy;
        public IWebProxy Proxy
        {
            get { return _proxy; }
            set { _proxy = value; }
        }

        private string _html;
        public string Html
        {
            get { return _html; }
        }

        private Hashtable _headers = new Hashtable();
        public Hashtable Headers
        {
            get { return _headers; }
        }

        private string _location;
        public string Location
        {
            get { return _location; }
        }

        private bool _sendReferer = true;
        public bool SendReferer
        {
            get { return _sendReferer; }
            set { _sendReferer = value; }
        }

        private HttpStatusCode _statusCode;
        public HttpStatusCode StatusCode
        {
            get { return _statusCode; }
        }

        private int _timeout;
        public int Timeout
        {
            get { return _timeout; }
            set { _timeout = value; }
        }

        private DateTime _lastUpdateDT = DateTime.Now;
        public DateTime LastUpdateDateTime
        {
            get { return _lastUpdateDT; }
        }

        private int waitTime = 3;
        public int WaitTime
        {
            get { return waitTime; }
        }

        public HttpStatusCode Request(string requestUri, string method, string postData)
        {
            string uri = BaseUri + requestUri;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

            if (Proxy != null)
                request.Proxy = Proxy;

            request.UserAgent = UserAgent;
            request.Accept = Accept;
            request.Method = method;
            request.KeepAlive = true;
            request.AllowAutoRedirect = false;

            if (SendReferer)
                request.Referer = PreviousUri != null ? PreviousUri : uri;

            foreach (string key in Headers.Keys)
                request.Headers.Add(key, Headers[key].ToString());

            string cookiesString = "";
            foreach (KeyValuePair<string, string> key in _cookies)
                cookiesString += key.Key + "=" + key.Value + "; ";

            if (cookiesString != "")
                request.Headers.Add(HttpRequestHeader.Cookie, cookiesString);

            request.ContentType = method == "POST" ? "application/x-www-form-urlencoded" : ContentType;

            PreviousUri = uri;

            if (Certificate != null)
                request.ClientCertificates.Add(Certificate);

            if (Timeout != 0)
                request.Timeout = Timeout;

            if (postData != null)
            {
                using (Stream st = request.GetRequestStream())
                {
                    Encoding e = Encoding.GetEncoding("windows-1251");

                    byte[] bytes = e.GetBytes(postData);
                    st.Write(bytes, 0, bytes.Length);
                }
            }

            _html = "";

            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            using (Stream sm = resp.GetResponseStream())
            using (StreamReader sr = new StreamReader(sm, Encoding.Default))
            {
                _statusCode = resp.StatusCode;
                _location = resp.Headers["Location"];

                _html = sr.ReadToEnd();

                if (resp.ResponseUri.AbsoluteUri.StartsWith(BaseUri) == false)
                    BaseUri = resp.ResponseUri.Scheme + "://" + resp.ResponseUri.Host;

                // specific to "http:\\vkontakte.ru" host cookies handling

                if (!string.IsNullOrEmpty(resp.Headers["Set-Cookie"]))
                {
                    string s = resp.Headers["Set-Cookie"];
                    Regex r = new Regex("(?<1>remix\\w+)=(?<2>\\S*);", RegexOptions.Compiled);

                    for (Match m = r.Match(s); m.Success; m = m.NextMatch())
                        _cookies[m.Groups[1].ToString()] = m.Groups[2].ToString();
                }
            }

            RequestUri = request.RequestUri;

            return StatusCode;
        }

        public HttpStatusCode Get(string requestUri, bool saveLastUpdateDT)
        {
            if (saveLastUpdateDT)
                Thread.Sleep(waitTime * 1000);

            Request(requestUri, "GET", null);

            for (int i = 0; i < 10; i++)
            {
                if (StatusCode == HttpStatusCode.Redirect ||
                    StatusCode == HttpStatusCode.RedirectKeepVerb ||
                    StatusCode == HttpStatusCode.RedirectMethod)
                {
                    if (Location == null)
                        break;

                    Uri uri = new Uri(new Uri(PreviousUri), Location);

                    BaseUri = uri.Scheme + "://" + uri.Host;
                    requestUri = uri.AbsolutePath + uri.Query;

                    Request(requestUri, "GET", null);
                }
                else
                    break;
            }

            if (saveLastUpdateDT)
                _lastUpdateDT = DateTime.Now;
            else
                Thread.Sleep(waitTime * 1000);

            return StatusCode;
        }

        public HttpStatusCode Post(string requestUri, string postData, bool saveLastUpdateDT)
        {
            if (saveLastUpdateDT)
                Thread.Sleep(waitTime * 1000);

            Request(requestUri, "POST", postData);

            for (int i = 0; i < 10; i++)
            {
                bool post = false;

                switch (StatusCode)
                {
                    case HttpStatusCode.MultipleChoices:  // 300
                    case HttpStatusCode.MovedPermanently: // 301
                    case HttpStatusCode.Found:            // 302
                    case HttpStatusCode.SeeOther:         // 303
                        break;

                    case HttpStatusCode.TemporaryRedirect: // 307
                        post = true;
                        break;

                    default:
                        return StatusCode;
                }

                if (Location == null)
                    break;

                Uri uri = new Uri(new Uri(PreviousUri), Location);

                BaseUri = uri.Scheme + "://" + uri.Host;
                requestUri = uri.AbsolutePath + uri.Query;

                Request(requestUri, post ? "POST" : "GET", post ? postData : null);
            }

            if (saveLastUpdateDT)
                _lastUpdateDT = DateTime.Now;
            else
                Thread.Sleep(waitTime * 1000);

            return StatusCode;
        }

        public void LoadCertificate(string fileName)
        {
            Certificate = X509Certificate.CreateFromCertFile(fileName);
        }
    }
}
